package util;

import javax.media.opengl.GL2;
import javax.media.opengl.glu.GLU;
import javax.media.opengl.glu.GLUquadric;

import com.jogamp.opengl.util.texture.Texture;

import core.Planet;

/**
 * TODO Séparer la création 2D de la 3D en 2 classes.<br>
 * TODO une sphere = un ellipsoide particulier => prendre en compte ce type de cas là plutot que de faire une fonction
 * spécifique.<br>
 * 
 * @author sebastien
 */
public class Util3D {

	/** Le point central */
	public static final Point3D CENTER_POINT = new Point3D(0.0, 0.0, 0.0);
	private static final int STACKS = 50;
	private static final int SLICES = 50;
	private static final int OVOID_SCALE = 10;
	private static final double SCALE = 1000;

	// Fields
	public static SphericalGeometrics geomS = new SphericalGeometrics();
	public static CartesianGeometrics geomC = new CartesianGeometrics();

	// 1D
	/**
	 * Dessine un point 3D à l'écran
	 * 
	 * @param point Le point
	 */
	public static void drawPoint(final GL2 gl, final Point3D xyz) {
		gl.glVertex3d(xyz.getX(), xyz.getY(), xyz.getZ());
	}

	/**
	 * Dessine un point � l'�cran
	 * 
	 * @param point Le point
	 */
	public static void drawPoint(final GL2 gl, double x, double y, double z) {
		gl.glVertex3d(x, y, z);
	}

	// 2D
	/**
	 * Construit un carr� de c�t� scale et commen�ant au point xyz.
	 * 
	 * @param gl
	 * @param scale
	 * @param xyz
	 * @return
	 */
	public static void drawSquare(final GL2 gl, int scale, Point3D xyz) {
		gl.glBegin(GL2.GL_LINES);
		gl.glVertex3d(xyz.getX(), xyz.getY(), xyz.getZ());
		gl.glVertex3d(xyz.getX() + scale, xyz.getY(), xyz.getZ());
		gl.glVertex3d(xyz.getX() + scale, xyz.getY(), xyz.getZ());
		gl.glVertex3d(xyz.getX() + scale, xyz.getY() + scale, xyz.getZ());
		gl.glVertex3d(xyz.getX() + scale, xyz.getY() + scale, xyz.getZ());
		gl.glVertex3d(xyz.getX(), xyz.getY() + scale, xyz.getZ());
		gl.glVertex3d(xyz.getX(), xyz.getY() + scale, xyz.getZ());
		gl.glVertex3d(xyz.getX(), xyz.getY(), xyz.getZ());
		gl.glEnd();
	}

	/**
	 * Construit les axes
	 * 
	 * @param gl
	 * @param begin
	 * @param end
	 */
	public static void drawAxes(final GL2 gl, int begin, int end) {
		gl.glBegin(GL2.GL_LINES);
		gl.glColor3f(0.0f, 1.0f, 0.7f); // set the color of the axe x
		gl.glVertex3f(begin, 0.0f, 0.0f);
		gl.glVertex3f(end, 0.0f, 0.0f);
		gl.glColor3f(1.0f, 0.7f, 0.0f); // set the color of the axe y
		gl.glVertex3f(0.0f, begin, 0.0f);
		gl.glVertex3f(0.0f, end, 0.0f);
		gl.glColor3f(1.0f, 0.0f, 0.7f); // set the color of the axe z
		gl.glVertex3f(0.0f, 0.0f, begin);
		gl.glVertex3f(0.0f, 0.0f, end);
		gl.glEnd();
	}

	/**
	 * Equation d'une ellipse : x²/a²+y²/b² = 1 avec a demi-grand-axe et b demi-petit axe.<br/>
	 * Soit y²/b² = 1 - x²/a², soit y² = b²(1-x²/a²), soit y = racineCarre(b²(1-x²/a²)).
	 * 
	 * @deprecated
	 * @param gl
	 * @param halfSmallAxe demi-grand-axe
	 * @param halfBigAxe demi-petit axe
	 */
	@Deprecated
	public static void drawEllipse(final GL2 gl, double halfSmallAxe, double halfBigAxe) {
		gl.glBegin(GL2.GL_POINTS);
		gl.glColor3f(0.2f, 1.0f, 0.2f); // set the color
		for (double x = -100.0; x < 100.0; x = x + 1 / SCALE) {
			double y = geomC.ellipseEquation(x, halfSmallAxe, halfBigAxe);
			gl.glVertex3d(x, y, 0.0);
			gl.glVertex3d(x, -y, 0.0);
		}
		gl.glEnd();
	}

	/**
	 * Dessine une ellipse. <br>
	 * Equation d'une ellipse polaire : r(theta) = p/(1 + e*cos(theta))
	 * 
	 * @param parameters param�tres de la
	 * @param p le rayon
	 * @param excentricity L'excentricit� de l'ellipse :
	 *            <ul>
	 *            <li>e=0 => cercle</li>
	 *            <li>0 < e < 1 => ellipse</li>
	 *            <li>e=1 => parabole</li>
	 *            <li>e>1 => hyperbole</li>
	 *            </ul>
	 */
	public static void drawEllipse(Parameters parameters, double p, double excentricity) {
		GL2 gl = parameters.getGl();
		gl.glBegin(parameters.getMode());
		double theta = 0.0;
		while (theta <= 2 * Math.PI) {
			Point3D point = geomS.polarPoint(geomS.ellipseEquation(p, excentricity, theta), theta);
			drawPoint(gl, point);
			theta = theta + Geometrics.SPHERE_SCALE;
		}
		gl.glEnd();
	}

	/**
	 * Dessine une figure suivant la super formule.
	 * 
	 * @param parameters parameters
	 * @param p param�tres de la super formule.
	 */
	public static void drawSuperFormula(Parameters parameters, SuperFormulaParameters p) {
		GL2 gl = parameters.getGl();
		gl.glBegin(parameters.getMode());
		gl.glColor3f(1.0f, 1.0f, 1.0f);
		double psi = 0.0;
		while (psi <= 2 * Math.PI) {
			Point3D point = geomS
					.polarPoint(geomS.superFormulaEquation(p.getA(), p.getB(), p.getM(), p.getN1(), p.getN2(),
							p.getN3(), psi), psi);
			drawPoint(gl, point);
			psi = psi + Geometrics.SPHERE_SCALE;
		}
		gl.glEnd();
	}

	/**
	 * Equation d'un ovoide de Kepler : y = racineCarre(x^(3/2) -x�).
	 * 
	 * @param gl
	 */
	public void drawKeplerOvoid(Parameters param) {
		GL2 gl = param.getGl();
		gl.glBegin(GL2.GL_POINTS);
		gl.glColor3f(0.2f, 1.0f, 0.2f); // set the color
		for (double x = -1.0; x < 1.0; x = x + 0.01) {
			double y = geomC.keplerOvoid(x);
			gl.glVertex3d(OVOID_SCALE * x, OVOID_SCALE * y, 5.0);
			gl.glVertex3d(OVOID_SCALE * x, -OVOID_SCALE * y, 5.0);
		}
		gl.glEnd();
	}

	/**
	 * @param param
	 */
	public static void drawKeplerOvoid_v2(Parameters param, double a) {
		GL2 gl = param.getGl();
		gl.glBegin(GL2.GL_LINE_STRIP);
		gl.glColor3f(0.2f, 1.0f, 0.2f); // set the color
		double theta = 0.0;
		while (theta < 2 * Math.PI) {
			Point3D point = geomS.polarPoint(geomS.keplerOvoid(a, theta), theta);
			drawPoint(gl, point);
			theta = theta + SphericalGeometrics.OVOID_SCALE;
		}
		gl.glEnd();
	}

	// 3D
	/**
	 * Construit la grille d'affichage
	 * 
	 * @param gl
	 */
	public static void draw3DGrid(final GL2 gl, int begin, int end, int scale) {
		gl.glBegin(GL2.GL_LINES);
		gl.glColor3f(1.0f, 1.0f, 1.0f); // set the color of the line
		for (int i = begin; i < end; i = i + scale) {
			for (int j = begin; j < end; j = j + scale) {
				for (int k = begin; k < end; k = k + scale) {
					drawCube(gl, scale, new Point3D(j, i, k));
				}
			}
		}
		gl.glEnd();
	}

	/**
	 * Construit un cube de c�t� scale et d�marrant au point xy.
	 * 
	 * @param gl
	 * @param scale La taille d'une ar�te du cube
	 * @param xy Le point de d�part
	 */
	public static void drawCube(final GL2 gl, int scale, Point3D xyz) {
		drawSquare(gl, scale, xyz);
		gl.glVertex3d(xyz.getX(), xyz.getY(), xyz.getZ());
		gl.glVertex3d(xyz.getX(), xyz.getY(), xyz.getZ() + scale);
		gl.glVertex3d(xyz.getX(), xyz.getY() + scale, xyz.getZ());
		gl.glVertex3d(xyz.getX(), xyz.getY() + scale, xyz.getZ() + scale);
		gl.glVertex3d(xyz.getX() + scale, xyz.getY() + scale, xyz.getZ());
		gl.glVertex3d(xyz.getX() + scale, xyz.getY() + scale, xyz.getZ() + scale);
		gl.glVertex3d(xyz.getX() + scale, xyz.getY(), xyz.getZ());
		gl.glVertex3d(xyz.getX() + scale, xyz.getY(), xyz.getZ() + scale);
		drawSquare(gl, scale, new Point3D(xyz.getX(), xyz.getY(), xyz.getZ() + scale));
	}

	/**
	 * Dessine une sphere de centre c et de rayon r.
	 * 
	 * @param gl
	 * @param r Le rayon de la sphere
	 * @param c Le centre de la sphere
	 */
	public static void drawSphere(Parameters param, Double r, Point3D c) {

		GL2 gl = param.getGl();
		gl.glBegin(param.getMode());
		gl.glColor3f(1.0f, 1.0f, 1.0f);

		double theta = 0.0;
		double psi;
		while (theta <= 2 * Math.PI) {
			psi = 0.0;
			while (psi <= 2 * Math.PI) {
				Point3D point = geomS.sphericalPoint(r, theta, psi);
				drawPoint(gl, point);
				psi = psi + Geometrics.SPHERE_SCALE;
			}
			theta = theta + Geometrics.SPHERE_SCALE;
		}

		gl.glEnd();
	}

	/**
	 * Version 2 de la cr�ation d'une sphere. Une sphere est un ellipsoide particulier : demi-grand axe et demi-petit
	 * axe �gaux.
	 * 
	 * @param param
	 * @param r
	 * @param c
	 */
	public static void drawSphere_v2(Parameters param, Double r, Point3D c) {
		drawEllipsoide(param, r, r, c);
	}

	/**
	 * Dessine une ellipsoide.
	 * 
	 * @param param param�tres d'affichage (couche graphique et mode d'affichage)
	 * @param a (demi grand axe) > b
	 * @param b (demi axe moyen)
	 * @param p foyer
	 */
	public static void drawEllipsoide(Parameters param, double a, double b, Point3D p) {

		GL2 gl = param.getGl();
		gl.glBegin(param.getMode());
		gl.glColor3f(1.0f, 1.0f, 1.0f);

		double theta = 0.0;
		double psi;
		while (theta <= 2 * Math.PI) {
			psi = 0.0;
			while (psi <= 2 * Math.PI) {
				double x = Transformations.xFromSphericalToCartesian3D(a, theta, psi);
				double y = Transformations.yFromSphericalToCartesian3D(a, theta, psi);
				double z = Transformations.zFromSphericalToCartesian3D(b, theta, psi);
				Point3D point = new Point3D(x, y, z);
				drawPoint(gl, point);
				psi = psi + Geometrics.SPHERE_SCALE;
			}
			theta = theta + Geometrics.SPHERE_SCALE;
		}
		gl.glEnd();
	}

	/**
	 * Dessine des orbites de planètes
	 * 
	 * @param parameters
	 */
	public static void drawOrbitalPlanets(Parameters parameters) {

		GL2 gl = parameters.getGl();
		// Mercure
		gl.glColor3f(0.0f, 1.0f, 0.2f);
		drawEllipse(parameters, 2.0, 0.20563069);
		// Venus
		gl.glColor3f(0.2f, 1.0f, 0.2f);
		drawEllipse(parameters, 4.0, 0.00677323);
		// Terre
		gl.glColor3f(0.4f, 1.0f, 0.2f);
		drawEllipse(parameters, 6.0, 0.01671022);
		// Mars
		gl.glColor3f(0.6f, 1.0f, 0.2f);
		drawEllipse(parameters, 8.0, 0.09341233);
		// Jupiter
		gl.glColor3f(0.8f, 1.0f, 0.2f);
		drawEllipse(parameters, 20.0, 0.04839266);

	}

	/**
	 * Dessine une planète à l'écran
	 * 
	 * @param gl
	 * @param glu
	 * @param radius
	 * @param distance
	 */
	public static void drawPlanet(GL2 gl, GLU glu, double radius, double distance, Texture texture) {
		gl.glTranslated(0.0f, 0.0f, -distance);

		GLUquadric planetQuadric = glu.gluNewQuadric();
		glu.gluQuadricTexture(planetQuadric, true);
		glu.gluQuadricDrawStyle(planetQuadric, GLU.GLU_FILL);
		glu.gluQuadricNormals(planetQuadric, GLU.GLU_EXTERIOR);
		glu.gluQuadricOrientation(planetQuadric, GLU.GLU_INSIDE);

		texture.enable(gl);
		texture.bind(gl);

		// Draw the sphere
		glu.gluSphere(planetQuadric, radius, SLICES, STACKS);
		glu.gluDeleteQuadric(planetQuadric);

		drawPlanetAxes(gl, radius);
		gl.glTranslated(0.0f, 0.0f, distance);
	}

	/**
	 * Dessine une planète
	 * 
	 * @param gl
	 * @param glu
	 * @param radius
	 * @param distance
	 */
	public static void drawPlanet(GL2 gl, GLU glu, Planet planet) {
		gl.glTranslated(0.0f, 0.0f, -planet.getDistanceFromSun());

		// Set material properties.
//		float[] rgba = { 1f, 1f, 1f };
//		gl.glMaterialfv(GL.GL_FRONT, GL2.GL_AMBIENT, rgba, 0);
//		gl.glMaterialfv(GL.GL_FRONT, GL2.GL_SPECULAR, rgba, 0);
//		gl.glMaterialf(GL.GL_FRONT, GL2.GL_SHININESS, 0.5f);

		GLUquadric planetQuadric = glu.gluNewQuadric();
		glu.gluQuadricTexture(planetQuadric, true);
		glu.gluQuadricDrawStyle(planetQuadric, GLU.GLU_FILL);
		glu.gluQuadricNormals(planetQuadric, GLU.GLU_FLAT);
		glu.gluQuadricOrientation(planetQuadric, GLU.GLU_OUTSIDE);
		
		// Draw the sphere
		glu.gluSphere(planetQuadric, planet.getRadius(), SLICES, STACKS);
		glu.gluDeleteQuadric(planetQuadric);
		
		drawPlanetAxes(gl, planet.getRadius());
		gl.glTranslated(0.0f, 0.0f, planet.getDistanceFromSun());
	}

	/**
	 * Dessine les axes de la planètes
	 * 
	 * @param gl
	 * @param radius
	 */
	private static void drawPlanetAxes(GL2 gl, double radius) {
		// Axes de la planète
		gl.glBegin(GL2.GL_LINES);
		gl.glColor3d(0.0, 1.0, 0.7); // set the color of the axe x
		double begin = -2 * radius;
		gl.glVertex3d(begin, 0.0, 0.0);
		double end = 2 * radius;
		gl.glVertex3d(end, 0.0, 0.0);
		gl.glColor3d(1.0f, 0.7f, 0.0f); // set the color of the axe y
		gl.glVertex3d(0.0f, begin, 0.0f);
		gl.glVertex3d(0.0f, end, 0.0f);
		gl.glColor3f(1.0f, 0.0f, 0.7f); // set the color of the axe z
		gl.glVertex3d(0.0f, 0.0f, begin);
		gl.glVertex3d(0.0f, 0.0f, end);
		gl.glEnd();
	}
}
